using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Multiplicity.Runtime.Serialization.Binary;

namespace Multiplicity.Reflection {
    public class Module : PortableExecutable {

        const int CorHeaderDirIndex = 14;
        const int MaxNameLength = 511;

        readonly CorHeader corHeader;
        readonly StorageHeader storageHeader;
        readonly long storageOffset;

        readonly USHeap usHeap;
        readonly BlobHeap blobHeap; 
        readonly GuidHeap guidHeap;
        readonly StringHeap stringHeap;
        readonly MetadataStream metadataStream;
        

        MetadataTables tables;
        TypeDef[] types;
        string name;
        Guid mvid;

        public Module(byte[] image)
            : base(image) {

            PEDataDirectory corHdrData = PEHeader.DataDirectories[CorHeaderDirIndex];

            using (BinaryParser br = OpenDirectory(corHdrData)) {
                this.corHeader = br.ReadRecord<CorHeader>();
            }

            using (BinaryParser br = OpenDirectory(corHeader.Metadata)) {
                this.storageOffset = br.Position;
                this.storageHeader = StorageHeader.Parse(br);
            }

            foreach (StorageHeapHeader hdr in storageHeader.Heaps) {
                switch (hdr.Name) {
                    case StringHeap.Name:
                        this.stringHeap = new StringHeap(this, hdr, storageOffset);
                        break;

                    case GuidHeap.Name:
                        this.guidHeap = new GuidHeap(this, hdr, storageOffset);
                        break;

                    case BlobHeap.Name:
                        this.blobHeap = new BlobHeap(this, hdr, storageOffset);
                        break;

                    case USHeap.Name:
                        this.usHeap = new USHeap(this, hdr, storageOffset);
                        break;

                    case MetadataStream.Name:
                        this.metadataStream = new MetadataStream(this, hdr, storageOffset);
                        break;

                    default:
                        MetadataException.ThrowUnknownStorageHeap(hdr.Name);
                        break;
                }
            }

            ValidateStorage();

            ParseModuleRow();

            ParseTables();
        }

        public StorageHeader StorageHeader {
            get { return storageHeader; }
        }

        public string Name {
            get { return name; }
        }

        public Guid ModuleVersionID {
            get { return mvid; }
        }

        public TypeDef[] GetTypes() {
            return types;
        }

        internal MetadataTables Tables {
            get { return tables; }
        }

        internal StringHeap Strings {
            get { return stringHeap; }
        }

        internal MetadataStream Metadata {
            get { return metadataStream; }
        }

        internal BlobHeap Blob {
            get { return blobHeap; }
        }

         

        void ValidateStorage() {
            if (guidHeap == null) MetadataException.ThrowStorageHeapNotFound(GuidHeap.Name);
            if (stringHeap == null) MetadataException.ThrowStorageHeapNotFound(StringHeap.Name);
            if (metadataStream == null) MetadataException.ThrowStorageHeapNotFound(MetadataStream.Name);
        }

        void ParseModuleRow() {
            ModuleRow mod = metadataStream.ReadRecord<ModuleRow>();

            this.name = stringHeap[mod.NameIndex];
            this.mvid = guidHeap[mod.MvidI];

            if (string.IsNullOrEmpty(name) || name.Length > MaxNameLength) {
                MetadataException.ThrowInvalidModuleName();
            }
        }

        void ParseTables() {

            this.tables = new MetadataTables(metadataStream);

            this.types = new TypeDef[tables.TypeDef.Count];

            for (uint i = 0; i < types.Length; i++) {
                types[i] = new TypeDef(this, i);
            }

        }

        internal TypeInfo FindTypeInfo(uint token) {
            MetadataTableType table = (MetadataTableType)(token >> 24);
            uint rid = (token & 0x00ffffff) - 1;

            switch (table) {
                case MetadataTableType.TypeDef:
                    return types[rid];

                case MetadataTableType.TypeRef:
                case MetadataTableType.TypeSpec:
                default:
                    MetadataException.ThrowInvalidToken(token);
                    return null;
            }
        }
        internal class MetadataTables {
            public readonly MetadataTable<TypeRefRow> TypeRef;
            public readonly MetadataTable<TypeDefRow> TypeDef;
            public readonly MetadataTable<FieldRow> Fields;

            internal MetadataTables(MetadataStream stream) {
                TypeRef = new MetadataTable<TypeRefRow>(stream);
                TypeDef = new MetadataTable<TypeDefRow>(stream);
                Fields = new MetadataTable<FieldRow>(stream);
            }


        }
    }
}
